
package com.avrio.json;

// <editor-fold defaultstate="collapsed" desc="import">
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import com.avrio.json.JsonObject.Member;
// </editor-fold>

@SuppressWarnings("serial")
public class JsonObject extends JsonValue implements Iterable<Member> {

    private final List<String> names;
    private final List<JsonValue> values;
    private transient HashIndexTable table;

// <editor-fold defaultstate="collapsed" desc="JsonObject()">

    public JsonObject() {
        names = new ArrayList<>();
        values = new ArrayList<JsonValue>();
        table = new HashIndexTable();
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="JsonObject()">

    public JsonObject(JsonObject object) {
        this(object, false);
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="JsonObject()">

    private JsonObject(JsonObject object, boolean unmodifiable) {

        if(object == null) {
            throw new NullPointerException("object is null");
        }
        if(unmodifiable) {
            names = Collections.unmodifiableList(object.names);
            values = Collections.unmodifiableList(object.values);
        }
        else {
            names = new ArrayList<String>(object.names);
            values = new ArrayList<JsonValue>(object.values);
        }
        table = new HashIndexTable();
        updateHashIndex();
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="readFrom()">

    public static JsonObject readFrom(Reader reader) throws IOException {
        return JsonValue.readFrom(reader).asObject();
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="readFrom()">

    public static JsonObject readFrom(String string) {
        return JsonValue.readFrom(string).asObject();
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="unmodifiableObject()">

    public static JsonObject unmodifiableObject(JsonObject object) {
        return new JsonObject(object, true);
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="add()">

    public JsonObject add(String name, int value) {
        add(name, valueOf(value));
        return this;
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="add()">

    public JsonObject add(String name, long value) {
        add(name, valueOf(value));
        return this;
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="add()">

    public JsonObject add(String name, float value) {
        add(name, valueOf(value));
        return this;
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="add()">

    public JsonObject add(String name, double value) {
        add(name, valueOf(value));
        return this;
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="add()">

    public JsonObject add(String name, boolean value) {
        add(name, valueOf(value));
        return this;
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="add()">

    public JsonObject add(String name, String value) {
        add(name, valueOf(value));
        return this;
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="add()">

    public JsonObject add(String name, JsonValue value) {

        if(name == null) {
            throw new NullPointerException("name is null");
        }
        if(value == null) {
            throw new NullPointerException("value is null");
        }
        table.add(name, names.size());
        names.add(name);
        values.add(value);

        return this;
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="set()">

    public JsonObject set(String name, int value) {
        set(name, valueOf(value));
        return this;
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="set()">

    public JsonObject set(String name, long value) {
        set(name, valueOf(value));
        return this;
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="set()">

    public JsonObject set(String name, float value) {
        set(name, valueOf(value));
        return this;
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="set()">

    public JsonObject set(String name, double value) {
        set(name, valueOf(value));
        return this;
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="set()">

    public JsonObject set(String name, boolean value) {
        set(name, valueOf(value));
        return this;
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="set()">

    public JsonObject set(String name, String value) {
        set(name, valueOf(value));
        return this;
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="set()">

    public JsonObject set(String name, JsonValue value) {

        if(name == null) {
            throw new NullPointerException("name is null");
        }
        if(value == null) {
            throw new NullPointerException("value is null");
        }
        int index = indexOf(name);
        if(index != -1) {
            values.set(index, value);
        }
        else {
            table.add(name, names.size());
            names.add(name);
            values.add(value);
        }

        return this;
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="remove()">

    public JsonObject remove(String name) {

        if(name == null) {
            throw new NullPointerException("name is null");
        }
        int index = indexOf(name);
        if(index != -1) {
            table.remove(index);
            names.remove(index);
            values.remove(index);
        }

        return this;
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="get()">

    public JsonValue get(String name) {

        if(name == null) {
            throw new NullPointerException("name is null");
        }
        int index = indexOf(name);

        return index != -1 ? values.get(index) : null;
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="size()">

    public int size() {
        return names.size();
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="isEmpty()">

    public boolean isEmpty() {
        return names.isEmpty();
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="names()">

    public List<String> names() {
        return Collections.unmodifiableList(names);
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="iterator()">

    public Iterator<Member> iterator() {

        final Iterator<String> namesIterator = names.iterator();
        final Iterator<JsonValue> valuesIterator = values.iterator();
        return new Iterator<JsonObject.Member>() {

            public boolean hasNext() {
                return namesIterator.hasNext();
            }

            public Member next() {
                String name = namesIterator.next();
                JsonValue value = valuesIterator.next();
                return new Member(name, value);
            }

            public void remove() {
                throw new UnsupportedOperationException();
            }

        };
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="write()">

    @Override
    protected void write(JsonWriter writer) throws IOException {
        writer.writeObject(this);
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="isObject()">

    @Override
    public boolean isObject() {
        return true;
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="asObject()">

    @Override
    public JsonObject asObject() {
        return this;
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="hashCode()">

    @Override
    public int hashCode() {

        int result = 1;
        result = 31 * result + names.hashCode();
        result = 31 * result + values.hashCode();
        return result;
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="equals()">

    @Override
    public boolean equals(Object obj) {

        if(this == obj) {
            return true;
        }
        if(obj == null) {
            return false;
        }
        if(getClass() != obj.getClass()) {
            return false;
        }

        JsonObject other = (JsonObject) obj;
        return names.equals(other.names) && values.equals(other.values);
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="indexOf()">

    int indexOf(String name) {

        int index = table.get(name);
        if(index != -1 && name.equals(names.get(index))) {
            return index;
        }

        return names.lastIndexOf(name);
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="readObject()">

    private synchronized void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException {

        inputStream.defaultReadObject();
        table = new HashIndexTable();
        updateHashIndex();
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="updateHashIndex()">

    private void updateHashIndex() {

        int size = names.size();
        for(int i = 0; i < size; i++) {
            table.add(names.get(i), i);
        }
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="class Member()">

    public static class Member {

        private final String name;
        private final JsonValue value;

        Member(String name, JsonValue value) {
            this.name = name;
            this.value = value;
        }

        public String getName() {
            return name;
        }

        public JsonValue getValue() {
            return value;
        }

        @Override
        public int hashCode() {

            int result = 1;
            result = 31 * result + name.hashCode();
            result = 31 * result + value.hashCode();
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if(this == obj) {
                return true;
            }
            if(obj == null) {
                return false;
            }
            if(getClass() != obj.getClass()) {
                return false;
            }
            Member other = (Member) obj;

            return name.equals(other.name) && value.equals(other.value);
        }
    }
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="class HashIndexTable()">

    static class HashIndexTable {

        private final byte[] hashTable = new byte[32];

        public HashIndexTable() {
        }

        public HashIndexTable(HashIndexTable original) {
            System.arraycopy(original.hashTable, 0, hashTable, 0, hashTable.length);
        }

        void add(String name, int index) {

            int slot = hashSlotFor(name);
            if(index < 0xff) {
                // increment by 1, 0 stands for empty
                hashTable[slot] = (byte) (index + 1);
            }
            else {
                hashTable[slot] = 0;
            }
        }

        void remove(int index) {

            for(int i = 0; i < hashTable.length; i++) {
                if(hashTable[i] == index + 1) {
                    hashTable[i] = 0;
                }
                else if (hashTable[i] > index + 1) {
                    hashTable[i]--;
                }
            }
        }

        int get(Object name) {
            int slot = hashSlotFor(name);
            return(hashTable[slot] & 0xff) - 1;
        }

        private int hashSlotFor(Object element) {
            return element.hashCode() & hashTable.length - 1;
        }

    }
// </editor-fold>

}
